/*
 * Copyright (C) 2005-present, 58.com.  All rights reserved.
 * Use of this source code is governed by a BSD type license that can be
 * found in the LICENSE file.
 */

import 'dart:convert';
import 'dart:io';

import 'package:analyzer/dart/analysis/analysis_context.dart';
import 'package:analyzer/dart/analysis/analysis_context_collection.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/dart/ast/ast.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:path/path.dart';
import 'transformer.dart';

var baseWidget = [
  'StatefulWidget',
  'StatelessWidget',
  'Widget',
  'CustomScrollView',
  'ScrollView',
  'ListView',
  'GridView',
];

var baseAPI = [
  'Alignment',
  'EdgeInsets',
  'TextStyle',
  'BoxDecoration',
  'DecorationImage',
  'NetworkImage',
  'Rect',
  'OffsetBase',
  'Radius',
  'RRect',
  'RSTransform',
  'BorderRadiusGeometry',
  'RenderComparison',
  'Axis',
  'VerticalDirection',
  'AxisDirection',
];

String get outputPrefix => 'sdk';

Future<void> parseSdkFile(String sdkName, String filePath, String output) async {
  await _generateSdkFile(sdkName, filePath, output);
}

Future<void> saveIntoFile(String result, String outputDir, String fileName) async {
  var file = File('$outputDir/$fileName')..createSync(recursive: true);
  await file.writeAsString(result);
  await Process.start('dartfmt', ['-w', '$outputDir']);
}

Future<String> parseDir(Directory dir) async {
  var files = await dir
      .list(recursive: true)
      .map((element) => element.absolute.path)
      .where((event) => event.endsWith('.dart') && (transformer.skipSource.indexWhere((skip) => event.endsWith(skip)) == -1))
      .toList();
  var collection = AnalysisContextCollection(includedPaths: files);
  var components = <ComponentParts>[];
  for (var path in files) {
    var context = collection.contextFor(path);
    var w = await processFile(context, path);
    if (w?.components?.isNotEmpty != true) continue;
    components.add(w!);
  }
  return _generateWidget(components: components);
}

Future<void> parseFlutter(String? flutterSDK) async {
  assert(flutterSDK != null);
  var frameworkAPI = [
    'packages/flutter/lib/src/widgets/',
    'packages/flutter/lib/src/painting/',
    'packages/flutter/lib/src/material/',
    'packages/flutter/lib/src/cupertino/',
    'packages/flutter/lib/src/rendering/',
    'packages/flutter/lib/src/animation/'
  ];
  var includedPaths = <String>[];
  for (var dirPath in frameworkAPI) {
    var dir = Directory('$flutterSDK/$dirPath');
    if (!await dir.exists()) continue;
    var files = await dir
        .list(recursive: true)
        .map((element) => element.absolute.path)
        .where((event) => event.endsWith('.dart') && (transformer.skipSource.indexWhere((skip) => event.endsWith(skip)) == -1))
        .toList();
    includedPaths.addAll(files);
  }

  await _analyzeFramework(includedPaths);
  await Process.start('dartfmt', ['-w', '$outputPrefix/lib/src/widgets']);
}

const shrinking = {
  'cupertino': 'c',
  'widgets': 'w',
  'painting': 'p',
  'material': 'm',
  'rendering': 'r',
  'a': 'a',
};

Map _getFlutterVersion() {
  var output = Process.runSync('flutter', ['--version', '--machine']);
  var jsonString = jsonDecode(output.stdout.toString());
  return jsonString;
}

Future<void> _analyzeFramework(List<String> paths) async {
  var cc = AnalysisContextCollection(includedPaths: paths);

  const VERSION = '0.0.1';
  var flutter = _getFlutterVersion();
  var flutterVersion = flutter['frameworkVersion'];
  var dartVersion = flutter['dartSdkVersion'];

  var buffer = StringBuffer('''
  // This file is generated by Fair, do not edit manually!
  // Updated on ${DateTime.now()}
  
  const fairVersion = '$VERSION';
  const flutterVersion = '$flutterVersion';
  const dartVersion = '$dartVersion';
  ''');

  var widgets = <Component>[];
  var bindingBuffer = StringBuffer('''
  // This file is generated by Fair, do not edit manually!
  // Updated on ${DateTime.now()}
  
  ''');
  var imports = <String>[];
  var components = <ComponentParts>[];
  for (var path in paths) {
    var context = cc.contextFor(path);
    var w = await processFile(context, path);
    if (w == null || w.components?.isNotEmpty != true) continue;
    widgets.addAll(w.components!);
    var n = _generateFileName(path);
    // name shrinking
    var index = n.indexOf('_');
    if (index != -1) {
      var s = shrinking.entries.firstWhere((element) => n.startsWith(element.key), orElse: () => MapEntry('a', 'a'));
      n = s.value;
    }
    w.aliasGroup = n;
    components.add(w);
  }

  components.sort((a, b) => a.aliasGroup?.compareTo(b.aliasGroup ?? '') ?? 0);
  shrinking.forEach((key, value) {
    var c = components.where((element) => element.aliasGroup == value).toList();
    if (c.isEmpty) return;
    var file = File('$outputPrefix/lib/src/widgets/\$\$$value.dart')..createSync(recursive: true);
    imports.add('\$\$$value');
    var isCupertino = c.first.isCupertino ?? false;
    var buffer = StringBuffer('''// This file is generated by Fair, do not edit manually!\n
import 'package:flutter/${isCupertino ? "cupertino.dart" : "material.dart"}';\n''');

    var allImports = c.fold(<String>[], (List<String> p, e) {
      if (e.imports != null) {
        p.addAll(e.imports ?? []);
      }
      return p;
    });
    allImports.forEach((element) => buffer.writeln('import \'$element\';'));

    var l = c.fold(<String>[], (List<String> p, e) {
      if (e.lines != null) {
        p.addAll(e.lines ?? []);
      }
      return p;
    });
    var lines = l.toList();
    lines.sort((a, b) => a.startsWith('import') ? -1 : (b.startsWith('import') ? 1 : 0));
    lines.forEach((element) => buffer.writeln('$element'));
    var body = c.fold(<String>[], (List<String> p, e) {
      if (e.body != null) {
        p.add(e.body ?? '');
      }
      return p;
    });
    buffer.writeln('''var p = () => {''');
    body.forEach((element) => buffer.writeln('$element'));
    buffer.writeln('};');
    file.writeAsStringSync(buffer.toString());
  });

  for (var i = 0; i < imports.length; i++) {
    var n = imports[i];
    n = n.replaceAll('\$', '\\\$');
    bindingBuffer.writeln('import \'$n.dart\' as \$$i;');
  }

  bindingBuffer.writeln('''
  mixin \$BindingImpl {
    final provider = [
  ''');

  for (var i = 0; i < imports.length; i++) {
    bindingBuffer.writeln('\$$i.p,');
  }

  bindingBuffer.writeln('];}');
  var f = File('$outputPrefix/lib/src/widgets/all.dart')..createSync(recursive: true);
  await f.writeAsString(bindingBuffer.toString());
  var widgetCount = widgets.where((element) => element.isWidget == true).length;
  var apiCount = widgets.length - widgetCount;
  buffer.writeln('''
  const widgetCount = $widgetCount;
  const apiCount = $apiCount;
  const widgetNames = {
  ''');
  widgets.forEach((element) {
    buffer.writeln('\'${element.name}\': ${element.isWidget},');
  });
  buffer.writeln('};');
  await File('$outputPrefix/lib/src/widgets/version.dart').writeAsString(buffer.toString());
}

Future<String> _generateWidget({List<ComponentParts?>? components, Future<ComponentParts> Function()? builder}) async {
  var c = components ?? (builder == null ? null : [await builder()]);
  if (c == null || c.first == null) return '';
  var isCupertino = c.first!.isCupertino ?? false;
  var buffer = StringBuffer('''
  import 'package:flutter/${isCupertino ? "cupertino.dart" : "material.dart"}';
  import 'package:fair/fair.dart';
  import 'package:fair_version/fair_version.dart';
  ''');

  var allImports = c.fold(<String>[], (List<String> p, e) {
    if (e?.imports != null) {
      p.addAll(e?.imports ?? []);
    }
    return p;
  });
  allImports.forEach((element) => buffer.write('import \'$element\';\n'));

  var l = c.fold(<String>[], (List<String> p, e) {
    if (e?.lines != null) {
      p.addAll(e?.lines ?? []);
    }
    return p;
  });
  var lines = l.toList();
  lines.sort((a, b) => a.startsWith('import') ? -1 : (b.startsWith('import') ? 1 : 0));
  lines.forEach((element) => buffer.write('$element\n'));
  var body = c.fold(<String>[], (List<String> p, e) {
    if (e?.body != null) {
      p.add(e?.body ?? '');
    }
    return p;
  });
  buffer.write('''
  
    class AppGeneratedModule extends GeneratedModule {
    @override
    Map<String, dynamic> components() {
      return {
  ''');
  body.forEach((element) => buffer.write('$element\n'));
  buffer.write('''};
    }
  ''');

  buffer.write('''
  @override
  Map<String, bool> mapping() {
      return const {
  ''');
  c.forEach((element) => element?.components?.forEach((e) {
        buffer.write('\'${e.name}\': ${e.isWidget},\n');
      }));
  buffer.write('''
      };
      }
      }
  ''');
  return buffer.toString();
}

Future _generateSdkFile(String? sdkName, String filePath, String output) async {
  var collection = AnalysisContextCollection(includedPaths: [filePath]);
  var context = collection.contextFor(filePath);
  var c = await processFile(context, filePath, analysisExports: true);
  if (c == null) return '';
  var isCupertino = c.isCupertino ?? false;
  var buffer = StringBuffer('''
  import 'package:flutter/${isCupertino ? "cupertino.dart" : "material.dart"}';
  ''');
  var fileName = basename(filePath);
  var clzName = fileName.replaceAll('.dart', '');
  if (sdkName?.isNotEmpty != true) {
    sdkName = clzName;
  }
  buffer.write('''
  import 'package:$sdkName/$fileName';
  ''');

  var lines = c.lines?.toList();
  lines?.sort((a, b) => a.startsWith('import') ? -1 : (b.startsWith('import') ? 1 : 0));
  lines?.forEach((element) => buffer.write('$element\n'));
  var body = c.body;
  buffer.write('''
  var ${clzName}_component = {
  ''');
  buffer.write('$body\n');
  buffer.write('};');

  await saveIntoFile(buffer.toString(), output, 'fair_${clzName}.dart');
}

Future<ComponentParts?> processFile(AnalysisContext context, String path, {bool analysisExports = false}) async {
  var session = context.currentSession;
  var result = await session.getUnitElement(path) as UnitElementResult;
  var element = result.element;
  var elementsList = <CompilationUnitElement?>[element];
  //analysis exports file
  if (analysisExports) {
    //Most of SDK entrance files export other files.
    //We need analysis all of them when we are compiling.
    var exports = result.element.enclosingElement.libraryExports;
    var exportsUnits = exports.map((e) => e.exportedLibrary?.definingCompilationUnit).toList();
    elementsList.addAll(exportsUnits);
  }

  var exposedAPI = <ClassExposed>[];
  elementsList.forEach((element) {
    exposedAPI.addAll(_visit(element, analysisExports));
  });

  var count = exposedAPI.length;
  print('😀 $count widgets found inside $path');
  var isCupertino = path.contains('/cupertino/');

  var buffer = StringBuffer();
  var clzName = basename(path).replaceAll('.dart', '').replaceAll('.fair', '');
  var imports = transformer.getImports(clzName);
  var lines = transformer.getLines(clzName, isCupertino);
  // constructor api
  var hasConstructor = false;
  exposedAPI.forEach((e) {
    var hasNamedConstructor = false;
    var defaultCache = <String, dynamic>{};
    e.constructor?.forEach((element) {
      if (element.parameters != null && (element.parameters?.isNotEmpty ?? false)) {
        hasNamedConstructor = true;
      }
      defaultCache = _writeMethod(buffer, element.name, element, defaultCache);
    });
    e.functionParameters?.forEach((element) {
      _writeFunctionParameter(buffer, element.toString(), element, defaultCache);
    });
    if (hasNamedConstructor) {
      e.fields?.forEach((element) {
        var field = element;
        buffer.write('\'${field.name}.${field.field}\': ${field.name}.${field.field},');
      });
    } else if (e.fields?.isNotEmpty == true) {
      var name = e.name;
      buffer.write('\'$name\': {');
      e.fields?.forEach((e) {
        var field = e;
        buffer.write('\'${field.field}\': ${field.name}.${field.field},');
      });
      buffer.write('},');
    }

    e.staticMethods?.forEach((element) {
      defaultCache = _writeMethod(buffer, element.name, element, defaultCache);
    });
  });
  if (!hasConstructor && exposedAPI.isEmpty) return null;
  return ComponentParts(
    exposedAPI
        .map((e) => Component(
              e.name,
              e.constructor != null && e.constructor?.isNotEmpty == true
                  ? e.constructor![0].isWidget
                  : ((e.fields?.isNotEmpty == true && e.fields!.elementAt(0).isWidget == true) ||
                      (e.staticMethods?.isNotEmpty == true && e.staticMethods!.elementAt(0).isWidget == true)),
            ))
        .toList(growable: false),
    buffer.toString(),
    imports: imports,
    lines: lines,
    isCupertino: isCupertino,
    clzName: clzName,
  );
}

class ComponentParts {
  final bool? isCupertino;
  final List<String>? imports;
  final List<String>? lines;
  final List<Component>? components;
  String? aliasGroup;
  final String? body;
  final String? clzName;

  ComponentParts(
    this.components,
    this.body, {
    this.imports,
    this.lines,
    this.isCupertino,
    this.clzName,
  });
}

class Component {
  final String? name;
  final bool? isWidget;

  Component(this.name, this.isWidget);
}

String _generateFileName(String path) {
  var fileName = path.split('packages/flutter/lib/src/')[1].replaceAll('/', '_');
  return fileName;
}

Map<String, dynamic> _writeMethod(StringBuffer buffer, String? name, Method element, Map<String, dynamic> defaultCache) {
  buffer.write('\'$name\': (props) => $name(');
  if (element.parameters != null && (element.parameters?.isNotEmpty ?? false)) {
    for (var i = 0; i < element.parameters!.length; i++) {
      var p = element.parameters![i];
      if (!transformer.isPropSupported(p.name)) continue;
      String prop;
      // JSON属性中可能存在，浮点写成整型，需要兼容
      var isDouble = p.type == 'double';
      var isList = p.type == 'List';

      // 处理List类型数据问题
      var cName = '';
      if (isList) {
        if (p.displayName != null && p.displayName!.contains('<')) {
          cName = '<' + p.displayName!.split('<')[1].split('>')[0] + '>';
        }
      }

      if (p.defaultValueCode != null) {
        if (isDouble) {
          prop = 'props[\'${p.name}\']?.toDouble() ?? ${p.defaultValueCode},';
        } else if (isList) {
          prop = 'as${cName}(props[\'${p.name}\']) ?? ${p.defaultValueCode},';
        } else {
          prop = 'props[\'${p.name}\'] ?? ${p.defaultValueCode},';
        }

        defaultCache[p.name ?? ''] = p.defaultValueCode;
      } else if (defaultCache[p.name] != null) {
        prop = isDouble
            ? 'props[\'${p.name}\']?.toDouble() ?? ${defaultCache[p.name]},'
            : isList
                ? 'as${cName}(props[\'${p.name}\']) ?? ${defaultCache[p.name]},'
                : 'props[\'${p.name}\'] ?? ${defaultCache[p.name]},';
        print('💕 using cached default value  ${element.name} ${p.name}=> ${defaultCache[p.name]}');
      } else {
        if (p.isOptional == true) {
          prop = isDouble
              ? 'props[\'${p.name}\']?.toDouble(),'
              : isList
                  ? 'as${cName}(props[\'${p.name}\']),'
                  : 'props[\'${p.name}\'],';
        } else {
          prop = isDouble
              ? 'props[\'${p.name}\']?.toDouble() ?? 0,'
              : isList
                  ? 'as${cName}(props[\'${p.name}\']) ?? const [],'
                  : 'props[\'${p.name}\'],';
        }
      }
      var namedDeclare = '${p.name}: $prop';
      var positionDeclare = isDouble ? 'props[\'pa\'][$i]?.toDouble(),' : 'props[\'pa\'][$i],';
      buffer.write(p.isNamed == true
          ? namedDeclare
          : p.isOptionalPositional == true
              ? prop.replaceAll('props[\'${p.name}\']', 'props[\'pa\'][$i]')
              : positionDeclare);
    }
    var params = element.parameters?.fold('', (String? value, p) => ((value ?? '') + (p.isNamed == true ? '${p.type} ${p.name}, ' : '${p.name}, ')));
    print('➡️ $name({$params})');
  } else {
    print('➡️ $name()');
  }
  buffer.write('),');
  return defaultCache;
}

Map<String, dynamic> _writeFunctionParameter(StringBuffer buffer, String name, FunctionParameter element, Map<String, dynamic> defaultCache) {
  buffer.write('\'$name\': ');
  if (element.typeArgument != null) {
    buffer.write('<${element.typeArgument}>');
  }
  buffer.write('(props) => ');
  buffer.write('(');

  if (element.parameters != null && element.parameters!.isNotEmpty) {
    for (var i = 0; i < element.parameters!.length; i++) {
      var p = element.parameters![i];
      buffer.write('${p.type} ${p.name}, ');
    }

    var params = element.parameters?.fold('', (String value, p) => value + (p.isNamed == true ? '${p.type} ${p.name}, ' : '${p.name}, '));
    print('➡️ $name({$params})');
  } else {
    print('➡️ $name()');
  }

  buffer.write(')');
  buffer.write('{ return (props[\'block\'])');
  if (element.returnType != 'void') {
    buffer.write('as ${element.returnType}');
  }
  buffer.write('; },');

  return defaultCache;
}

var transformer = TransformProxy();

bool _matchType(InterfaceType? type, List<String>? widgets, {InterfaceElement? classElement}) {
  if (type == null) return false;
  var hit = (widgets ?? []).indexWhere((element) => element == type.name) != -1;
  return hit || _tryInternalCheck(type, widgets, classElement) || _matchType(type.superclass, widgets);
}

bool _tryInternalCheck(InterfaceType type, List<String>? widgets, classElement) {
  // try {
  //   if (type.superclass != null &&
  //       type.superclass.name == 'Object' &&
  //       classElement is ClassElementImpl &&
  //       classElement.linkedNode is ClassDeclarationImpl) {
  //     var superName = (classElement.linkedNode as ClassDeclarationImpl)
  //         .extendsClause
  //         .superclass
  //         .name
  //         .name;
  //     return widgets.indexWhere((element) => element == superName) != -1;
  //   }
  // } catch (e) {
  //   print(e);
  // }
  return false;
}

class ClassExposed extends Exposed {
  final List<Constructor>? constructor;
  final List<ConstField>? fields;
  final List<Method>? staticMethods;
  final List<FunctionParameter>? functionParameters;

  ClassExposed(
    String name, {
    this.constructor,
    this.staticMethods,
    this.fields,
    this.functionParameters,
  }) : super(name);

  int get size => (constructor?.length ?? 0) + (fields?.length ?? 0) + (functionParameters?.length ?? 0);
}

abstract class Exposed {
  final String? name;

  Exposed(this.name);
}

class Constructor extends Method {
  Constructor(String name, {List<Parameter>? parameters, bool? isWidget}) : super(name, parameters: parameters, isWidget: isWidget);

  @override
  String toString() {
    return name ?? '';
  }
}

class Method extends Exposed {
  final List<Parameter>? parameters;
  final bool? isWidget;

  Method(String name, {this.parameters, this.isWidget = true}) : super(name);

  @override
  String toString() {
    return name ?? '';
  }
}

class ConstField extends Exposed {
  final String? field;
  final bool? isWidget;

  ConstField(String name, {this.field, this.isWidget = true}) : super(name);

  @override
  String toString() {
    return '$name.$field';
  }
}

class FunctionParameter extends Exposed {
  final String? className;
  final List<Parameter>? parameters;
  final String? returnType;
  final String? typeArgument;

  FunctionParameter(String name, {this.className, this.parameters, this.returnType, this.typeArgument}) : super(name);

  @override
  String toString() {
    return '$className#$name';
  }
}

class Parameter {
  final String? name;
  final String? type;
  final bool? isNamed;
  final bool? isOptional;
  final bool? isOptionalPositional;
  //暂时为了转化List
  final String? displayName;
  final String? defaultValueCode;

  Parameter({
    this.name,
    this.type,
    this.displayName,
    this.isNamed = false,
    this.isOptional = true,
    this.isOptionalPositional = false,
    this.defaultValueCode,
  });
}

bool _invalidElement(Element element) {
  if (!element.isPublic || element.hasDeprecated || element.hasVisibleForTesting) {
    return true;
  }
  // hasVisibleForTesting 与 isVisibleForTesting都不能正确识别注解visibleForTesting
  var meta = element.metadata;
  var isVisibleForTesting =
      meta.isNotEmpty ? meta.where((element) => element.isVisibleForTesting || element.toSource() == '@visibleForTesting') : null;
  return isVisibleForTesting != null && isVisibleForTesting.isNotEmpty;
}

/// 跳过的类型
var _blackList = [
   /// It has non-constant instances of IconData
   /// https://github.com/wuba/Fair/issues/244
   /// IconData 不经常使用，不移除的话，Flutter 没法做 tree-shake-icons, 这会导致包体积进一步增大
   /// 如果用户真的要用到，自定义 binding 并且使用 --no-tree-shake-icons 命令打包即可
   'IconData',
];

///
/// We need to compile all of constructions when compile the SDK files.
///
List<ClassExposed> _visit(CompilationUnitElement? unitElement, [bool isSdk = false]) {
  if (unitElement == null) return <ClassExposed>[];
  var exposed = <ClassExposed>[];
  // 枚举与class不同
  var apis = [...unitElement.classes, ...unitElement.enums];
  for (var classElement in apis) {
    if(classElement is ClassElement && _blackList.contains(classElement.name)) {
      continue; 
    }
    if ((classElement is ClassElement && classElement.isAbstract) || _invalidElement(classElement) || classElement.isSynthetic) {
      print('skip ' + classElement.name);
      continue;
    }
    var constructors = <Constructor>[];
    var functionParameters = <FunctionParameter>[];
    var isWidget = classElement.thisType is InterfaceType ? _matchType(classElement.thisType, baseWidget, classElement: classElement) : false;
    var isAPI = classElement.thisType is InterfaceType ? _matchType(classElement.thisType, baseAPI, classElement: classElement) : false;
    if (isWidget || isAPI || isSdk) {
      print('${classElement.name} 😀');
      print('constructors ➡️');
      for (var constructorElement in classElement.constructors) {
        if (!_invalidElement(constructorElement)) {
          String name;
          if (constructorElement.name == '') {
            name = classElement.name;
          } else {
            name = '${classElement.name}.${constructorElement.name}';
          }
          print(' $name');
          // params
          var parameters = <Parameter>[];
          if (constructorElement.parameters.isNotEmpty) {
            constructorElement.parameters.forEach((e) {
              parameters.add(Parameter(
                type: e.type.name,
                name: e.name,
                ///此处withNullability待测试
                displayName: e.type.getDisplayString(withNullability: false),
                isNamed: e.isNamed,
                isOptional: e.isOptional,
                defaultValueCode: e.defaultValueCode,
                isOptionalPositional: e.isOptionalPositional,
              ));

              if (e.type.name == null && e.type is FunctionType) {
                // var parameters = (e.type as FunctionType)
                //     .parameters
                //     .map((e) => Parameter(
                //           type: e.type.name ?? _parseFunctionName(e),
                //           name: e.name,
                //           isNamed: e.isNamed,
                //           isOptional: e.isOptional,
                //           defaultValueCode: e.defaultValueCode,
                //         ))
                //     .toList(growable: false);

                // functionParameters.add(FunctionParameter(e.name,
                //     className: name,
                //     parameters: parameters,
                //     returnType:
                //         (e.type as FunctionType).returnType.name ?? 'void',
                //     typeArgument: ((e.type as FunctionType)
                //                 ?.typeArguments
                //                 ?.isNotEmpty ??
                //             false)
                //         ? (e.type as FunctionType)?.typeArguments?.first?.name
                //         : null));
              }
            });
          }

          constructors.add(Constructor(name, parameters: parameters, isWidget: isWidget));
        }
      }
      print('constructors ⬅️️');
    }
    var fields = <ConstField>[];
    for (var fieldElement in classElement.fields) {
      if (!_invalidElement(fieldElement) && fieldElement.isConst) {
        print('  ❤️${classElement.name}.${fieldElement.name}');
        // 复用class类型（可能不一致）
        fields.add(ConstField(classElement.name, field: fieldElement.name, isWidget: isWidget));
      }
    }

    var staticMethods = <Method>[];
    for (var methodElement in classElement.methods) {
      String name;
      if (!_invalidElement(methodElement) && methodElement.isStatic) {
        name = '${classElement.name}.${methodElement.name}';
        print(' 👉$name');
      } else {
        continue;
      }
      List<Parameter>? parameters;
      if (methodElement.parameters.isNotEmpty) {
        parameters = methodElement.parameters
            .map(
                (e) => Parameter(type: e.type.name, name: e.name, isNamed: e.isNamed, isOptional: e.isOptional, defaultValueCode: e.defaultValueCode, isOptionalPositional: e.isOptionalPositional,))
            .toList(growable: false);
      }
      staticMethods.add(Method(name, parameters: parameters, isWidget: isWidget));
    }

    if (constructors.isNotEmpty || fields.isNotEmpty || staticMethods.isNotEmpty) {
      exposed.add(ClassExposed(
        classElement.name,
        constructor: constructors,
        staticMethods: staticMethods,
        fields: fields,
        functionParameters: functionParameters,
      ));
    }
  }
  return exposed;
}

// String _parseFunctionName(ParameterElement e) {
//   var element = (e.type.element as ElementImpl);
//   var alias = (element.linkedNode.parent as TypeAliasImpl);
//   return alias.name.toString();
// }
